package pfc;

import java.util.LinkedList;

import pfc.Constantes;
import pfc.Cliente;
import pfc.Conversacion;
import pfc.Usuario;
import pfc.Interfaz;

import pfc.mensaje.*;

import rice.p2p.commonapi.NodeHandle;

/**
 * Unifica las clases y ofrece sus funcionalidades al usuario.
 * @author Gabriel Reus Rodríguez
 * @version 1.0, 06/11/2010
 */

public class Aplicacion
{

	/**
	*Conversaciones manejadas.
	*/
	private LinkedList<Conversacion> conversaciones= new LinkedList<Conversacion>();
	/**
	*Usuarios que están conectados.
	*/
	private LinkedList<Usuario> usuarios= new LinkedList<Usuario>();
	/**
	* Usuario local que conecta al anillo.
	*/
	private Usuario usuario= null;
	private Cliente cl= null;
	private Interfaz interfaz=null;

	/**
	 * Constructor.
	 */
	public Aplicacion()
	{
		this.interfaz= new Interfaz(this);
	}

	public Cliente getCliente()
	{
		return this.cl;
	}

	/**
	* Conecta la aplicación al anillo FreePastry
	*/
	public void conectar(String contexto, String grupo,  String nick) throws Exception
	{
			if(contexto.equals("") || grupo.equals("") || nick.equals("") )
			{

				//Los parametros están mal.
				String causa="\n";
				if(contexto.equals(""))
				{
					causa+="\tEl contexto está vacio.\n";
				}
				if(grupo.equals(""))
				{
					causa+="\tEl grupo está vacio.\n";
				}
				if(nick.equals(""))
				{
					causa+="\tEl nick está vacio.\n";
				}
				throw new Exception (causa);			}
			this.cl= new Cliente();
			this.cl.setContexto(contexto);
			this.cl.setGrupo(grupo);

			//Inicio cliente.
	            	this.cl.initKBR("./config/easypastry-config.xml");
	            	this.cl.initDHT();
	            	this.cl.initCast(this);
 				    
	         	this.cl.start();
			//Una vez creado el nodo introduzco el nick del usuario.
			this.cl.putClave(this.cl.obtenIdNodo(),nick);
			//usuarios.add(new Usuario(nick,cl.obtenIdNodo()));
			
			//Creo el usuario que está conectado.
			this.usuario= new Usuario(nick,this.cl.obtenIdNodo(),"");
			this.cl.setConexion(true);
				
	}

	/**
	* Desconecta la aplicación del anillo FreePastry
	*/
	public void desconectar()
	{
		try
		{
			if(this.cl != null && this.cl.estaConectado())
			{
				this.cl.eliminarNodo();
				this.usuarios.clear();
				this.interfaz.actualizaUsuarios(usuarios);
				this.cl.setConexion(false);
			}
			else
			{
				throw new Exception("Se intenta desconectar la aplicación que no está conectada");
			}
		}
		catch(Exception e)
		{
			notificaExcepcion(e);			
		}

	}
	/**
	* Devuelve true si  la aplicación está conectada al anillo FreePastry.
	*/
	public boolean estaConectado()
	{
		 return this.cl.estaConectado();
	}

	/**
	* Envia un mensaje al usuario us.
	*/
	public void enviarMensaje(Usuario us, Mensaje m) throws Exception
	{
		cl.enviaObjeto(us.getId(),m);
	}

	/**
	* Envía un mensaje al grupo MultiCast
	*/
	public void enviarMensajeMultiCast(Mensaje m) throws Exception
	{
		this.cl.enviaObjetoMultiCast(m);
	}

	/**
	* Función que procesa los mensajes recibidos.
	*/
	public void recibirMensaje(Mensaje m)
	{
		int tipo=m.getTipo();
		switch(tipo)
		{
			case Constantes.MENSAJE_TIPO_TEXTO:
				//Si la conversacion es null es un mensaje multicast para el general.
				if(((Texto)m).getIdConversacion() == Constantes.NO_CONVERSACION)
				{
					interfaz.escribeMensajePrincipal(((Texto)m).getTextoFormato());
				}
				//Si no busco la conversacion que corresponda.
				else
				{
					Conversacion conv=null;
					try
					{
						conv=buscaConversacion(((Texto)m).getIdConversacion());
						//Agrego el mensaje al historial de la conversacion.
						conv.addMensaje(m);
						this.interfaz.getVentanaConversacion(conv).escribeGeneral( ((Texto)(m)).getTextoFormato() );
					}
					catch(Exception e)
					{
						notificaExcepcion(e);
						return;
					}

				}
			break;
			case Constantes.MENSAJE_TIPO_FICHERO:
				//Hago el cast necesario a Fichero y guardo el contenido a un fichero.
				try
				{
					((Fichero)m).guardaFichero(Constantes.MENSAJE_CARPETA_DEFECTO);
				}
				catch(Exception e)
				{
					notificaExcepcion(e);
					return;
				}
				try
				{
					Conversacion conv=buscaConversacion(m.getIdConversacion());
					conv.addMensaje(m);
					this.interfaz.getVentanaConversacion(conv).escribeGeneral( "Se ha recibido el fichero"+((Fichero)m).getNombre()+" del usuario: "+m.getEmisor().getNick() );
					
				}
				catch(Exception e)
				{
					//No se encontró la conversación.
					notificaExcepcion(e);
				}
				
				
			break;
			case Constantes.MENSAJE_TIPO_ADMINISTRACION:
				if(((Administracion)m).getOrden()==Constantes.MENSAJE_ORDEN_NUEVA_CONVERSACION)
				{
					//Crea la conversacion
					Conversacion conversacion= ((Administracion)m).getConversacion();
					if(conversacion == null)
					{
						System.out.println("Error la conversacion es null");
					}
					conversaciones.add(conversacion);
					//Inicia la ventana de la conversacion
					this.interfaz.addVentana(conversacion,this);
				}
				if(((Administracion)m).getOrden()==Constantes.MENSAJE_ORDEN_ABANDONA_CONVERSACION)
				{
					//Obtengo el remitente del mensaje y la conversacion y lo elimino de la conversacion.
					Conversacion conversacion= ((Administracion)m).getConversacion();
					

					try
					{
						Conversacion conversacion_local= buscaConversacion(conversacion.getId());
						Grupo grupo=  conversacion_local.getGrupo();						
						grupo.removeUsuario(((Administracion)m).getEmisor());
						this.interfaz.getVentanaConversacion(conversacion_local).escribeGeneral( "El usuario: "+m.getEmisor().getNick()+" ha abandonado la conversacion");
					}
					catch(Exception e)
					{
						notificaExcepcion(e);
					}
					//Actualizo las ventanas de conversacion necesarias.
					this.interfaz.actualizaConversaciones();
				}
			break;

			default:
			break;
		}
	}
	/**
	* Función que actualiza las listas de usuarios (tanto los usuarios conectados como las listas de las conversaciones) cuando conecta y desconecta un usuario.
	*/

	public void userUpdate(boolean joined,NodeHandle nh)
	{
		Usuario us=null;
		String id=nh.getId().toStringFull();
		if(joined)
		{
			//Nuevo usuario.
			String nick="";
			try
			{
				nick=id;
				us = new Usuario(nick,id);
				conectaUsuario(us);
				this.interfaz.actualizaUsuarios(usuarios);
			}
			catch(Exception e)
			{
				notificaExcepcion(e);
			}


		}
		else
		{
			//Usuario desconectado.
			desconectaUsuario(new Usuario("",id));
			this.interfaz.actualizaUsuarios(usuarios);
		}
	}

	/**
	* Agrega un usuario a la lista de usuarios conectados.
	*/
	private void conectaUsuario(Usuario us)
	{
		this.usuarios.add(us);
	}

	/**
	* Elimina un usuario de la lista de usuarios conectados y de las listas de usuarios de las conversaciones cuando desconecta un usuario.
	*/
	private void desconectaUsuario(Usuario us)
	{
		for(int i=0;i<usuarios.size();i++)
		{
			Usuario user=usuarios.get(i);
			if (user.getId().equals(us.getId()))
			{
				usuarios.remove(i);
				break;
			}
		}
		//Analizo conversacion por conversacion borrando el usuario si existe.
		for(int i=0;i<conversaciones.size();i++)
		{
			Grupo grp = (conversaciones.get(i)).getGrupo();
			try
			{
				grp.removeUsuario(us);
			}
			catch(Exception e)
			{
				//Usuario no encontrado por lo tanto no hace falta que haga nada y pasamos a la siguiente conversacion.
			}
		}
		//Actualizo las ventanas de conversacion necesarias.
		this.interfaz.actualizaConversaciones();
	}

	/**
	* Crea una nueva conversación
	*/
	public void nuevaConversacion(LinkedList participantes) throws Exception
	{
		//Busco los usuarios y creo el grupo.
		LinkedList<Usuario> usuarios_participantes= new LinkedList<Usuario>();
		for(int i=0;i<participantes.size();i++)
		{
			Usuario us= buscaUsuario((String)participantes.get(i));
			if(us!= null)
			{
				usuarios_participantes.add(us);
			}
		}
		if(usuarios_participantes.size() == 0)
		{
			//No hay usuarios.
			throw new Exception ("Se iba a crear una conversación vacia");
		}
		//Añado el usuario creador de la conversacion.
		usuarios_participantes.add(usuario);
		//Creo la conversacion.
		Grupo grp = new Grupo(usuarios_participantes);
		Conversacion conversacion= new Conversacion(grp);
		conversaciones.add(conversacion);
		//Lo notifico a los demás miembros (1 a 1).
		for(int i=0;i<usuarios_participantes.size();i++)
		{
			//Envia un mensaje al destinatario conforme se ha creado la conversacion.
			try
			{
				Usuario us= usuarios_participantes.get(i);
				if(!us.getId().equals(usuario.getId()))
				{
					enviarMensaje(us,new Administracion(this.cl.getGrupo(),conversacion,Constantes.MENSAJE_ORDEN_NUEVA_CONVERSACION,this.usuario));
				}
			}
			catch(Exception e)
			{
				notificaExcepcion(e);
			}
		}
		this.interfaz.addVentana(conversacion,this);
	}

	/**
	* Busca un usuario en la lista de usuarios conectados, devuelve null si no lo encuentra.
	*/
	private Usuario buscaUsuario(String id)
	{
		for(int i=0;i<usuarios.size();i++)
		{
			Usuario us=usuarios.get(i);
			if(id.equals(us.getId()))
			{	
				return us;
			}
		}
		return null;
	}

	/**
	* Busca una conversación en la lista de conversaciones que tiene abiertas
	*/
	private Conversacion buscaConversacion(int id_conversacion) throws Exception
	{
		for ( int i=0;i<conversaciones.size();i++)
		{
			Conversacion aux= conversaciones.get(i);
			if (aux.getId() == id_conversacion)
			{
				return aux;
			}
		}
		throw new Exception("No se encontró la conversación buscada");
	}

	/**
	* El usuario local abandona una conversación y lo notifica a los demás usuarios de la conversación.
	*/
	public void abandonaConversacion(Conversacion conversacion)
	{
		Grupo grp= conversacion.getGrupo();
		LinkedList<Usuario> lista_usuarios= grp.getUsuarios();
		for(int i=0;i<lista_usuarios.size();i++)
		{
			Usuario us= lista_usuarios.get(i);
			if(!us.getId().equals(usuario.getId()))
			{
				try
				{
					enviarMensaje(us,new Administracion(this.cl.getGrupo(),conversacion,Constantes.MENSAJE_ORDEN_ABANDONA_CONVERSACION,this.usuario));
				}
				catch(Exception e)
				{
					//Excepcion al enviar el mensaje de abandono de conversacion.
				}
			}
		}
		//Elimino la conversacion de la lista de conversaciones local.
		eliminaConversacion(conversacion);		
	}

	/**
	* Función para eliminar la conversación de la lista de conversaciones en curso.
	*/
	private void eliminaConversacion(Conversacion conversacion)
	{
		for(int i=0;i<conversaciones.size();i++)
		{
			Conversacion conv= conversaciones.get(i);
			if(conv.getId()== conversacion.getId())
			{
				conversaciones.remove(i);
				break;
			}
		}
	}

	/**
	*Función que implementa los procesos que se ejecutan al salir de la aplicación.
	*/
	public void salir()
	{
		this.desconectar();
		System.exit(0);
	}

	public Usuario getUsuario()
	{
		return this.usuario;
	}
	/**
	* Proceso de notificar una excepcion a la interfaz.
	*/
	public void notificaExcepcion(Exception e)
	{
		this.interfaz.notificaExcepcion(e);
	}
}
